using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditorInternal;
using UnityEngine;

namespace UnityGameFramework.Editor
{
    public class TypeSearch
    {
        private static readonly Assembly[] s_Assemblies = null;
        private static Dictionary<string, Assembly> s_AssemblyCollection;
        private HashSet<string> m_NameCollection;

        static TypeSearch()
        {
            s_Assemblies = AppDomain.CurrentDomain.GetAssemblies();
            s_AssemblyCollection = s_Assemblies.ToDictionary(a=> a.GetName().Name);
        }
        public string[] AssemblyNames
        {
            get
            {
                return this.GetAssemblyNameArray();
            }
        }

        public TypeSearch() 
        {
            m_NameCollection = new HashSet<string>();
        }

        public TypeSearch(string[] defaultAssemblyNames)
        {
            m_NameCollection = new HashSet<string>(defaultAssemblyNames.Length);
            AddAssemblyName(defaultAssemblyNames);
        }

        public TypeSearch AddAssemblyName(params string[] assemblyNames)
        {
            for (int i = 0; i < assemblyNames.Length; i++)
            {
                m_NameCollection.Add(assemblyNames[i]);
            }
            return this;
        }

        public TypeSearch AddAssemblyData(params AssemblyDefinitionData[] datas)
        {
            AddAssemblyName(datas.Select(d => d.name).ToArray());
            return this;
        }

        public TypeSearch AddAssemblyDefinition(params AssemblyDefinitionAsset[] definitions)
        {
            AddAssemblyName(definitions.Select(d => JsonUtility.FromJson<AssemblyDefinitionData>(d.text).name).ToArray());
            return this;
        }
        
        public TypeSearch RemoveAssemblyName(params string[] assemblyNames)
        {
            for (int i = 0; i < assemblyNames.Length; i++)
            {
                m_NameCollection.Remove(assemblyNames[i]);
            }
            return this;
        }

        public bool Contains(string assemblyName)
        {
            return m_NameCollection.Contains(assemblyName);
        }

        public void Clear()
        {
            m_NameCollection.Clear();
        }

        public string[] GetAssemblyNameArray()
        {
            return m_NameCollection.ToArray();
        }

        public IEnumerable<System.Type> Search(System.Func<System.Type,bool> searchFunction)
        {
            var assemblySelect = m_NameCollection.Select(
                n =>
                {
                    Assembly assembly = null;
                    if (s_AssemblyCollection.TryGetValue(n, out assembly))
                    {
                        return assembly;
                    }
                    try
                    {
                        assembly = Assembly.Load(n);
                    }
                    catch (System.Exception)
                    {
                        return null;
                    }
                    return assembly;
                }).Where(a => a != null);

            return assemblySelect.SelectMany(assembly => assembly.GetTypes().Where(searchFunction));
        }
    }
}
